package com.easyjoin.core;

import com.easyjoin.annotation.SelectColumn;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.sql.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

public class EasyForeignPropertyWriter extends BeanPropertyWriter {
    private final BeanPropertyWriter baseWriter;
    private final String selfColumn;
    private final SelectColumn[] targetColumn;
    private final String tableName;
    private final DataSource dataSource;
    private final boolean cache;
    // 缓存：
    // key=表名:外键字段:外键值，
    // value = 例如 {"name": "仓库A", "location": "北京"}
    private static final LoadingCache<String, ConcurrentHashMap<String, Optional<String>>> TARGET_VALUE_CACHE =
            Caffeine.newBuilder()
                    .maximumSize(1000)
                    .expireAfterWrite(5, TimeUnit.SECONDS)
                    .build(key -> new ConcurrentHashMap<>());

    private static final Logger log = LoggerFactory.getLogger(EasyForeignPropertyWriter.class);

    public EasyForeignPropertyWriter(BeanPropertyWriter baseWriter, String selfColumn, SelectColumn[] targetColumn, String tableName, boolean cache, DataSource dataSource) {
        super(baseWriter);
        this.baseWriter = baseWriter;
        this.selfColumn = selfColumn;
        this.targetColumn = targetColumn;
        this.tableName = tableName;
        this.cache = cache;
        this.dataSource = dataSource;
    }

    @Override
    public void serializeAsField(Object bean, JsonGenerator gen, SerializerProvider prov) throws Exception {
        // 先序列化原字段
        baseWriter.serializeAsField(bean, gen, prov);
        // 取得加上注解的字段值（外建值）
        Object selfColumnValue = baseWriter.get(bean);
        if (selfColumnValue == null) return;
        // 取得缓存
        ConcurrentHashMap<String, Optional<String>> cacheMap = getCacheMap(selfColumnValue);
        // 添加目标字段
        for (SelectColumn col : targetColumn) {
            String value = Optional.ofNullable(cacheMap.get(col.column()))
                    .flatMap(v -> v)
                    .orElse(null);
            String columnName = col.alias().isEmpty() ? underlineToCamel(col.column()) : col.alias();
            writeField(gen, columnName, value);
        }
    }


    private void writeField(JsonGenerator gen, String name, String value) throws IOException {
        if (value == null) gen.writeNullField(name);
        else gen.writeStringField(name, value);
    }

    private ConcurrentHashMap<String, Optional<String>> getCacheMap(Object selfColumnValue) {
        // 获取缓存 key
        String cacheKey = tableName + ":" + selfColumn + ":" + selfColumnValue;
        // 从缓存取 取不到会创建新的map
        ConcurrentHashMap<String, Optional<String>> cacheMap =
                TARGET_VALUE_CACHE.get(cacheKey);
        // 如果未开启缓存，则查询所有字段更新到缓存
        if(!cache){
            // 收集所有需要查询的字段名
            String[] targetColumnNames = Arrays.stream(targetColumn)
                    .map(SelectColumn::column)
                    .toArray(String[]::new);
            // 查询所有需要查询的字段数据
            Map<String, String> targetColumnData = getTargetColumnValue(selfColumnValue, selfColumn,
                    targetColumnNames, tableName, dataSource);
            // 写入缓存（即便没启用缓存，也可以作为预热用 便于已经开启缓存的可以直接命中）
            for (String col : targetColumnNames) {
                cacheMap.put(col, Optional.ofNullable(targetColumnData.get(col)));
            }
            return cacheMap;
        }

        // 如果使用缓存 则先查询缓存中缺失的字段
        List<String> missing = new ArrayList<>();
        for (SelectColumn col : targetColumn) {
            // 这里不会报空指针，这是idea静态检查的bug
            if (!cacheMap.containsKey(col.column())) {
                missing.add(col.column());
            }
        }
        // 若存在缺失字段，则仅查询缺失字段并合并到 cacheMap
        if (!missing.isEmpty()) {
            Map<String, String> missingColumnData = getTargetColumnValue(selfColumnValue, selfColumn,
                    missing.toArray(new String[0]), tableName, dataSource);

            // 把 missingColumnData的结果（包括 null）放入 cacheMap，使用 putIfAbsent 保证已有缓存不被覆盖
            for (String col : missing) {
                // 使用 Optional 表达可能为 null 的值（ConcurrentHashMap 不允许 null）
                cacheMap.putIfAbsent(col, Optional.ofNullable(missingColumnData.get(col)));
            }
        }
        return cacheMap;
    }

    @SneakyThrows
    public static Map<String, String> getTargetColumnValue(
            Object selfColumnValue,
            String selfColumn,
            String[] targetColumn,
            String tableName,
            DataSource dataSource) {

        Map<String, String> resultMap = new HashMap<>();
        String targetColumns = String.join(",", targetColumn);
        String querySql = "SELECT " + targetColumns + " FROM " + tableName + " WHERE " + selfColumn + " = ?";

        int rowCount = 0;

        try (Connection connection = dataSource.getConnection();
             PreparedStatement stmt = connection.prepareStatement(querySql)) {

            stmt.setObject(1, selfColumnValue);
            try (ResultSet rs = stmt.executeQuery()) {
                ResultSetMetaData metaData = rs.getMetaData();
                int columnCount = metaData.getColumnCount();

                while (rs.next()) {
                    rowCount++;

                    // 只取第一条结果
                    if (rowCount == 1) {
                        for (int i = 1; i <= columnCount; i++) {
                            String columnName = metaData.getColumnName(i);
                            Object columnValue = rs.getObject(i);
                            if (Arrays.asList(targetColumn).contains(columnName)) {
                                resultMap.put(columnName, columnValue == null ? null : columnValue.toString());
                            }
                        }
                    }
                }
            }

            // 若返回多条，打印警告日志
            if (rowCount > 1) {
                log.warn("[EasyForeign] 表：{} 中字段 {}值为 {},查询到 {} 条记录。理论应唯一，已默认取第一条，请检查数据唯一性。",
                        tableName, selfColumn, selfColumnValue, rowCount);
            }

        } catch (SQLException e) {
            log.error("[EasyForeign] 查询外键映射异常，表: {}, 字段: {}, 值: {}, 错误: {}",
                    tableName, selfColumn, selfColumnValue, e.getMessage(), e);
        }

        return resultMap;
    }


    /**
     * 下划线转驼峰
     */
    private static String underlineToCamel(String underline) {
        if (underline == null || underline.isEmpty()) {
            return underline;
        }
        StringBuilder result = new StringBuilder();
        boolean upperCase = false;
        for (int i = 0; i < underline.length(); i++) {
            char c = underline.charAt(i);
            if (c == '_') {
                upperCase = true;
            } else if (upperCase) {
                result.append(Character.toUpperCase(c));
                upperCase = false;
            } else {
                result.append(Character.toLowerCase(c));
            }
        }
        return result.toString();
    }


}
